#ifndef _COMPILATION_EXPRESSION_H_
#define _COMPILATION_EXPRESSION_H_

#include <compilation/Forward.h>

#include <iostream>
#include <memory>
#include <functional>

#include <compilation/Function.h>


namespace compilation
{

class VirtualExpression
{
public:

  typedef ExpressionType Type;
  typedef WorkListReusability Reusability;

private:

  Type m_type;

protected:

  void setType(Type typeSource)
  { assert(m_type == TExpressionUndefined); m_type = typeSource; }

public:

  VirtualExpression(void)
    : m_type(TExpressionUndefined) {}

  virtual ~VirtualExpression(void) {}

  Type type(void) const
  { return m_type; }

  template <Type _Type>
  bool hasType(void)
  { return m_type == _Type; }

  virtual VirtualExpression* clone(void) const
  { assert(false); return nullptr; }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent = std::function<void(void)>()) const = 0;

  virtual std::auto_ptr<VirtualType> newType(Function* function) const = 0;
};

class ConstantExpression
    : public VirtualExpression
{
private:

  int m_value;

public:

  ConstantExpression(int value)
    : m_value(value) { setType(TConstant); }

  const int& value(void) const
  { return m_value; }

  virtual VirtualExpression* clone(void) const
  { return new ConstantExpression(*this); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << m_value; }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(new BaseType(BaseType::VInt)); }
};

class CharExpression
    : public VirtualExpression
{
private:

  char m_value;

public:

  CharExpression(int valueSource)
    : m_value(valueSource) { setType(TChar); }

  const char& value(void) const
  { return m_value; }

  virtual VirtualExpression* clone(void) const
  { return new CharExpression(*this); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << m_value; }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(new BaseType(BaseType::VChar)); }
};

class StringExpression
    : public VirtualExpression
{
private:

  std::string m_value;

public:

  StringExpression(const std::string& valueSource)
    : m_value(valueSource) { setType(TString); }

  virtual VirtualExpression* clone(void) const
  { return new StringExpression(*this); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << '"' << m_value << '"'; }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(new PointerType(new BaseType(BaseType::VChar))); }
};

class LocalVariableExpression
    : public VirtualExpression
{
private:

  Scope m_scope;
  std::string m_name;
  int m_localIndex;

public:

  LocalVariableExpression(const std::string& name, int localIndex, Scope scope)
    : m_scope(scope), m_name(name), m_localIndex(localIndex)
  {  setType(TLocalVariable); }

  LocalVariableExpression(LocalVariableExpression const& copy) = default;

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << "[local " << m_localIndex << ": " << m_name << ']'; }

  virtual VirtualExpression* clone(void) const
  { return new LocalVariableExpression(*this); }

  std::string const& name(void) const
  { return m_name; }

  void rename(std::string const& nm)
  { m_name = nm; }

  int getLocalIndex(void) const
  { return m_localIndex; }

  void setLocalIndex(int index)
  { m_localIndex = index; }

  Scope& scope(void) { return m_scope; }

  int getFunctionIndex(Function& function) const;

  int getGlobalIndex(void) const
  { return m_scope.getFunctionIndex(m_localIndex); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(m_scope.getType(m_localIndex).clone()); }
};

class ParameterExpression
    : public VirtualExpression
{
private:

  std::string m_name;
  int m_localIndex;

public:

  ParameterExpression(const std::string& name, int localIndex)
    : m_name(name), m_localIndex(localIndex) { setType(TParameter); }

  ParameterExpression(ParameterExpression const& copy) = default;

  int getIndex(void) const
  { return m_localIndex; }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << "[parameter " << m_localIndex << ": " << m_name << ']'; }

  virtual VirtualExpression* clone(void) const
  { return new ParameterExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const;
};

class GlobalVariableExpression
    : public VirtualExpression
{
private:

  std::string m_name;
  int m_localIndex;

public:

  GlobalVariableExpression(const std::string& name, int localIndex)
    : m_name(name), m_localIndex(localIndex) { setType(TGlobalVariable); }

  GlobalVariableExpression(GlobalVariableExpression const& copy) = default;

  const int getIndex(void) const
  { return m_localIndex; }

  std::string const& name(void) const
  { return m_name; }

  void rename(std::string const& nm)
  { m_name = nm; }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << "[global " << m_localIndex << ": " << m_name << ']'; }

  virtual VirtualExpression* clone(void) const
  { return new GlobalVariableExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const;
};

class ComparisonExpression
    : public VirtualExpression
{
public:

  enum Operator
  {
    OUndefined,
    OCompareLess,
    OCompareLessOrEqual,
    OCompareEqual,
    OCompareDifferent,
    OCompareGreaterOrEqual,
    OCompareGreater
  };

private:

  Operator m_operator;
  std::auto_ptr<VirtualExpression> m_fst, m_snd;

public:

  ComparisonExpression(void)
    : m_operator(OUndefined) { setType(TComparison); }

  ComparisonExpression(ComparisonExpression const& copy)
    : VirtualExpression(copy)
    , m_operator(copy.m_operator)
    , m_fst(copy.m_fst.get() ? copy.m_fst->clone() : nullptr)
    , m_snd(copy.m_snd.get() ? copy.m_snd->clone() : nullptr) {}

  ComparisonExpression& setOperator(Operator operatorSource)
  {
    assert(m_operator == OUndefined);
    m_operator = operatorSource; return *this;
  }

  ComparisonExpression& setFst(VirtualExpression* fst)
  {
    m_fst.reset(fst);
    return *this;
  }

  ComparisonExpression& setSnd(VirtualExpression* snd)
  {
    m_snd.reset(snd);
    return *this;
  }

  VirtualExpression* getFst(void)
  { return m_fst.get(); }

  VirtualExpression* getSnd(void)
  { return m_snd.get(); }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new ComparisonExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(new BaseType(BaseType::VInt)); }
};

class UnaryOperatorExpression
    : public VirtualExpression
{
public:

  enum Operator
  {
    OUndefined,
    OMinus,
    ONeg
  };

private:

  Operator m_operator;
  std::auto_ptr<VirtualExpression> m_subExpression;

public:

  UnaryOperatorExpression(void)
    : m_operator(OUndefined) { setType(TUnaryOperator); }

  UnaryOperatorExpression(UnaryOperatorExpression const& copy)
    : VirtualExpression(copy)
    , m_operator(copy.m_operator)
    , m_subExpression(
        copy.m_subExpression.get()
        ? copy.m_subExpression->clone()
        : nullptr) {}

  UnaryOperatorExpression&
  setOperator(Operator operatorSource)
  {
    assert(m_operator == OUndefined);
    m_operator = operatorSource;
    return *this;
  }

  UnaryOperatorExpression&
  setSubExpression(VirtualExpression* subExpression)
  {
    m_subExpression.reset(subExpression);
    return *this;
  }

  VirtualExpression* getSubExpression(void)
  { return m_subExpression.get(); }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new UnaryOperatorExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return m_subExpression->newType(function); }
};

class BinaryOperatorExpression
    : public VirtualExpression
{
public:

  enum Operator
  {
    OUndefined,
    OPlus,
    OMinus,
    OTimes,
    ODivide
  };

private:

  Operator m_operator;
  std::auto_ptr<VirtualExpression> m_fst, m_snd;

public:

  BinaryOperatorExpression(void)
    : m_operator(OUndefined) { setType(TBinaryOperator); }

  BinaryOperatorExpression(BinaryOperatorExpression const& copy)
    : VirtualExpression(copy)
    , m_operator(copy.m_operator)
    , m_fst((copy.m_fst.get()) ? copy.m_fst->clone() : nullptr)
    , m_snd((copy.m_snd.get()) ? copy.m_snd->clone() : nullptr){}

  BinaryOperatorExpression& setOperator(Operator operatorSource)
  {
    assert(m_operator == OUndefined);
    m_operator = operatorSource;
    return *this;
  }

  BinaryOperatorExpression& setFst(VirtualExpression* fst)
  {
    m_fst.reset(fst);
    return *this;
  }

  BinaryOperatorExpression& setSnd(VirtualExpression* snd)
  {
    m_snd.reset(snd);
    return *this;
  }

  VirtualExpression* getFst(void)
  { return m_fst.get(); }

  VirtualExpression* getSnd(void)
  { return m_snd.get(); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new BinaryOperatorExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return m_fst->newType(function); }
};

class DereferenceExpression
    : public VirtualExpression
{
private:

  std::auto_ptr<VirtualExpression> m_subExpression;

public:

  DereferenceExpression(void)
  { setType(TDereference); }

  DereferenceExpression(DereferenceExpression const& copy)
    : VirtualExpression(copy)
    , m_subExpression(
        copy.m_subExpression.get()
        ? copy.m_subExpression->clone()
        : nullptr) {}

  DereferenceExpression& setSubExpression(VirtualExpression* subExpression)
  {
    m_subExpression.reset(subExpression);
    return *this;
  }

  VirtualExpression* getSubExpression(void)
  { return m_subExpression.get(); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new DereferenceExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  {
    std::auto_ptr<VirtualType> result = m_subExpression->newType(function);
    assert(dynamic_cast<const PointerType*>(result.get()));
    return std::auto_ptr<VirtualType>(
          reinterpret_cast<PointerType*>(result.get())->getSubType());
  }
};

class ReferenceExpression
    : public VirtualExpression
{
private:

  std::auto_ptr<VirtualExpression> m_subExpression;

public:

  ReferenceExpression(void)
  { setType(TDereference); }

  ReferenceExpression(ReferenceExpression const& copy)
    : VirtualExpression(copy)
    , m_subExpression(
        copy.m_subExpression.get()
        ? copy.m_subExpression->clone()
        : nullptr) {}

  ReferenceExpression& setSubExpression(VirtualExpression* subExpression)
  {
    m_subExpression.reset(subExpression);
    return *this;
  }

  VirtualExpression* getSubExpression(void)
  { return m_subExpression.get(); }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new ReferenceExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  {
    std::auto_ptr<VirtualType> result = m_subExpression->newType(function);
    return std::auto_ptr<VirtualType>(new PointerType(result.release()));
  }
};

class CastExpression
    : public VirtualExpression
{
private:

  std::auto_ptr<VirtualType> m_type;
  std::auto_ptr<VirtualExpression> m_subExpression;

public:

  CastExpression(void)
  { VirtualExpression::setType(TCast); }

  CastExpression(CastExpression const& copy)
    : VirtualExpression(copy)
    , m_type(copy.m_type.get() ? copy.m_type->clone() : nullptr)
    , m_subExpression(
        copy.m_subExpression.get()
        ? copy.m_subExpression->clone()
        : nullptr) {}

  CastExpression& setSubExpression(VirtualExpression* subExpression)
  {
    m_subExpression.reset(subExpression);
    return *this;
  }

  VirtualExpression* getSubExpression(void)
  { return m_subExpression.get(); }

  CastExpression& setType(VirtualType* type)
  {
    m_type.reset(type);
    return *this;
  }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new CastExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return std::auto_ptr<VirtualType>(m_type->clone()); }
};

class FunctionCallExpression
    : public VirtualExpression
{
private:

  Function* m_function;
  std::vector<VirtualExpression*> m_arguments;

public:

  FunctionCallExpression(Function& function)
    : m_function(&function) { setType(TFunctionCall); }

  FunctionCallExpression(FunctionCallExpression const& copy)
    : VirtualExpression(copy)
    , m_function(copy.m_function)
  {
    m_arguments.reserve(copy.m_arguments.size());

    for (VirtualExpression* expr : copy.m_arguments)
      m_arguments.push_back(expr->clone());
  }

  virtual ~FunctionCallExpression(void)
  {
    for (std::vector<VirtualExpression*>::iterator iIter = m_arguments.begin();
         iIter != m_arguments.end(); ++iIter)
      if (*iIter) delete (*iIter);

    m_arguments.clear();
  }

  FunctionCallExpression& addArgument(VirtualExpression* argument)
  {
    m_arguments.push_back(argument);
    return *this;
  }

  std::vector<VirtualExpression*>& getArguments(void)
  { return m_arguments; }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new FunctionCallExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* callerFunction) const;
};

class AssignExpression
    : public VirtualExpression
{
private:

  std::auto_ptr<VirtualExpression> m_lvalue;
  std::auto_ptr<VirtualExpression> m_rvalue;

public:

  AssignExpression(void)
  { setType(TAssign); }

  AssignExpression(AssignExpression const& copy)
    : VirtualExpression(copy)
    , m_lvalue(copy.m_lvalue.get() ? copy.m_lvalue->clone() : nullptr)
    , m_rvalue(copy.m_rvalue.get() ? copy.m_rvalue->clone() : nullptr) {}

  VirtualExpression const* getLValue(void) const
  { return m_lvalue.get(); }

  VirtualExpression* getLValue(void)
  { return m_lvalue.get(); }

  VirtualExpression const* getRValue(void) const
  { return m_rvalue.get(); }

  VirtualExpression* getRValue(void)
  { return m_rvalue.get(); }

  AssignExpression& setLValue(VirtualExpression* lvalue)
  {
    m_lvalue.reset(lvalue);
    return *this;
  }

  AssignExpression& setRValue(VirtualExpression* rvalue)
  {
    m_rvalue.reset(rvalue);
    return *this;
  }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;

  virtual VirtualExpression* clone(void) const
  { return new AssignExpression(*this); }

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  { return m_lvalue->newType(function); }
};

class PhiExpression
    : public VirtualExpression
{
private:

  std::auto_ptr<VirtualExpression> m_fst;
  GotoInstruction* m_fstFrom;

  std::auto_ptr<VirtualExpression> m_snd;
  GotoInstruction* m_sndFrom;

public:

  PhiExpression(void)
    : m_fstFrom(nullptr)
    , m_sndFrom(nullptr)
  { setType(TPhi); }

  PhiExpression& addReference(
      VirtualExpression* expression,
      GotoInstruction& gotoInstruction)
  {
    if (m_fst.get())
    {
      assert(!m_snd.get());
      m_snd.reset(expression);
      m_sndFrom = &gotoInstruction;
    }
    else
    {
      m_fst.reset(expression);
      m_fstFrom = &gotoInstruction;
    };
    return *this;
  }

  VirtualExpression* getFst(void)
  { return m_fst.get(); }

  VirtualExpression* getSnd(void)
  { return m_snd.get(); }

  virtual void print(
      std::ostream& out,
      std::function<void()> const& indent) const;

  virtual std::auto_ptr<VirtualType> newType(Function* function) const
  {
    return m_fst.get()
        ? m_fst->newType(function)
        : m_snd->newType(function);
  }
};


} // compilation

// Inline methods.
namespace compilation
{

inline int
LocalVariableExpression::getFunctionIndex(Function& function) const
{ return m_scope.getFunctionIndex(m_localIndex) + function.signature().size(); }

inline std::auto_ptr<VirtualType>
ParameterExpression::newType(Function* function) const
{ return std::auto_ptr<VirtualType>(function->signature().getTypeParameter(m_localIndex).clone()); }

inline std::auto_ptr<VirtualType>
GlobalVariableExpression::newType(Function* function) const
{ return std::auto_ptr<VirtualType>(function->getTypeGlobal(m_localIndex).clone()); }

inline std::auto_ptr<VirtualType>
FunctionCallExpression::newType(Function* callerFunction) const
{ return std::auto_ptr<VirtualType>(m_function->signature().getResultType().clone()); }

} // compilation

#endif // _COMPILATION_EXPRESSION_H_
